/**
 * @brief 与 MCU 时钟 / RTC 相关的实现
 * @author 张勇 / 2020-05-13
 */

#include <stdint.h>
#include <string.h>

#include <system_stm32f4xx.h> // for SystemCoreClock, SystemCoreClockUpdate()
#include <stm32f4xx_ll_bus.h>
#include <stm32f4xx_ll_rcc.h>
#include <stm32f4xx_ll_pwr.h>
#include <stm32f4xx_ll_rtc.h>
#include <stm32f4xx_ll_system.h> // for LL_FLASH_SetLatency

#include "share/cccompat.h"
#include "share/utils.h"
#include "share/calendar.h"
#include "share/unios.h"
#include "mcu.h"

#include "share/log.h"
#define LOGLEVEL LOG_DEBUG
#define LOGTAG   "MCU"
#include "share/log4app.h"

// 外设时钟实际值
static uint32_t _ahb_freq  = 16800000;
static uint32_t _apb1_freq = 42000000;
static uint32_t _apb2_freq = 84000000;
uint32_t mcu_clock_get_ahb_freq()  { return _ahb_freq;  }
uint32_t mcu_clock_get_apb1_freq() { return _apb1_freq; }
uint32_t mcu_clock_get_apb2_freq() { return _apb2_freq; }

std_err_t _mcu_system_core_clock_set_hse_pll(uint32_t hse_clock_freq, uint32_t core_clock_freq) {
    uint32_t pllm, plln;
    
    // 使能HSE时钟并等待就绪
    LL_RCC_HSE_Enable();
    while(LL_RCC_HSE_IsReady() != 1);
    
	// 设置主频相关的 CPU 供电参数
    // - 如果你要让 STM32F4 主频达到 168MHz、180MHz（如 F429/F446），必须选择 Voltage Scaling 1（LL_PWR_REGU_VOLTAGE_SCALE1）
    // - 如果频率较低（如 120MHz 以下），可以选择 Scale 2 或 Scale 3，以降低功耗。
    // - 设置 Voltage Scaling 通常早于或在 PLL 配置前
    LL_PWR_SetRegulVoltageScaling(LL_PWR_REGU_VOLTAGE_SCALE1);	

    // 2 设置 PLLCLK (可用于主系统时钟 SYSCLK/AHB) 和 PLL48CK (48MHz 用于 OTG FS, SDIO, RNG 的时钟)
    // PLL 时钟树: HSE -|
    //            HSI -|-> /M -> *N -|-> /P -> PLLCLK  -> AHB
    //                               |-> /Q -> PLL48CK -> USB-OTG-FS,SDIO,RNG
    // HSE 时钟为 8/24/25MHz 时，无论如何都无法同时获得 180MHz 的 PLLCLK/SYSCLK/AHB 时钟 和 48MHz 的 PLL48CK 时钟
    // 几个选择:
	//   - 方案1: SYSCLK 超频，PLL48CK 准确
    //     PLLCLK/SYSCLK = 192MHz = (HSE=25M) / (M=25) * (N=384) / (P=2)
    //           PLL48CK = 48MHz  = (HSE=25M) / (M=25) * (N=384) / (Q=8)
	//   - 方案2: SYSCLK 降频，PLL48CK 准确
    //     PLLCLK/SYSCLK = 168MHz = (HSE=25M) / (M=25) * (N=336) / (P=2)
    //           PLL48CK = 48MHz  = (HSE=25M) / (M=25) * (N=336) / (Q=7)
	//   - 方案3: SYSCLK 准确，PLL48CK 略微超频: 这也是绝大多数 STM32F4 官方固件和 CubeMX 默认的配置，实际应用中 USB 通常可用。
    //     PLLCLK/SYSCLK = 180MHz = (HSE=25M) / (M=25) * (N=360) / (P=2)
    //         PLL48CK = 51.43MHz = (HSE=25M) / (M=25) * (N=360) / (Q=7)
    //
    // 主频是168MHz的情况下选择 方案3: SYSCLK = 168MHz, PLL48CK = 48MHz
    // 主频是180MHz的情况下选择 方案3: SYSCLK = 180MHz, PLL48CK = 51.43MHz
	pllm = hse_clock_freq / 1000000;        // 分频系数
    plln = core_clock_freq / 500000;        // 倍频系数
    
	// !!! 注意: 下面两个函数调用中前 3 个参数是共用的: LL_RCC_PLLSOURCE_HSE, pllm, plln，所以必须是相同的 !!!    
    LL_RCC_PLL_ConfigDomain_SYS(LL_RCC_PLLSOURCE_HSE, pllm, plln, LL_RCC_PLLP_DIV_2); // 配置 PLLCLK    
    LL_RCC_PLL_ConfigDomain_48M(LL_RCC_PLLSOURCE_HSE, pllm, plln, LL_RCC_PLLQ_DIV_7); // 配置 PLL48CK

    // 使能PLL并等待就绪
    LL_RCC_PLL_Enable();
    while(LL_RCC_PLL_IsReady() != 1);
    
    #ifdef PWR_CR_ODEN
        // 设置主频相关的 CPU 供电参数
        // - 当你打算让芯片主频跑到 > 168MHz，如 180MHz 时（如 STM32F429/439/469/479），必须在设置 Voltage Scaling 1 的基础上，再启用 OverDrive。
        // - 对于 168MHz 及以下（如 F407/411/415），不需要 OverDrive，只需 Voltage Scaling 1。
        // - 设置 OverDrive 通常在 PLL 配置后，系统主频切换前
        LL_PWR_EnableOverDriveMode();
        while(LL_PWR_IsActiveFlag_OD() != 1);
    #endif

    // 配置总线分频
    LL_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1); _ahb_freq  = core_clock_freq;      // AHB不分频(最大 180MHz)
    LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_4) ; _apb1_freq = core_clock_freq / 4;  // APB1四分频(最大 45MHz)
    LL_RCC_SetAPB2Prescaler(LL_RCC_APB2_DIV_2) ; _apb2_freq = core_clock_freq / 2;  // APB2二分频(最大 90MHz)

    // 配置Flash等待周期
    LL_FLASH_SetLatency(LL_FLASH_LATENCY_5);         // 168MHz需要5个等待周期

    // 选择PLL作为系统时钟源
    LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_PLL);
    while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL);

    // 更新系统时钟变量
    SystemCoreClockUpdate();
    return STD_ERR_OK;
}

/// SystemCoreClock 变量声明见 system_stm32f4xx.h，定义见 system_stm32f4xx.c
uint32_t mcu_clock_get_sysfreq(void) {
    return SystemCoreClock;
}

#ifndef USE_HAL_DRIVER
/// @brief ms 计数器
volatile uint32_t _tick_count_ms = 0;
#endif

/// @brief 系统滴答时钟中断服务函数
_RAMCODE_ void SysTick_Handler(void) {
    #ifdef USE_HAL_DRIVER
        // 注意: HAL 库需要把 SysTick 配置成 1ms 一次中断，并在中断中更新计数值 !!!
        HAL_IncTick();
    #else
        _tick_count_ms++;
    #endif
}

/// 初始化系统滴答时钟: 成功之后 mcu_ms_xxx(), mcu_us_xxx() 相关函数才能正常工作 !!!
static std_err_t _systick_timer_init(void) {
    #ifdef USE_HAL_DRIVER
        // do nothing, 直接使用 HAL_Init() 调用 HAL_InitTick() 对 SysTick 进行初始化
        // SysTick 时钟源为 AHB
    #else
        // SysTick->CTRL[2:2] = 0 时钟源为 HCLK/8(MCU主时钟源/8)
        // SysTick->CTRL[2:2] = 1 时钟源为 AHB(MCU主时钟源)
        // 设置重装载计数值，以使 SysTick 定时器 1ms 产生一次中断
        SysTick->LOAD = SystemCoreClock - 1UL; // SysTick 时钟源为 AHB
        // Load the SysTick Counter Value
        SysTick->VAL  = 0UL;
        // Enable SysTick IRQ and SysTick Timer
        SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | // SysTick 时钟源为 AHB
                        SysTick_CTRL_TICKINT_Msk   |
                        SysTick_CTRL_ENABLE_Msk;
    #endif
    return STD_ERR_OK;
}

uint32_t mcu_clock_get_systick(void) {
    // 获取 SysTick 定时器计数值
    uint32_t systick = SysTick->VAL;
    // 把 24 位向下计数值转换为正向计数值
    systick = 0x1000000 - systick;
    
    // 返回时加上 ms 换算出来的部分
    #ifdef USE_HAL_DRIVER
        // HAL 的 SysTick 就是 ms 时间戳，但本函数要返回的是 SysTick 计数值
        return (HAL_GetTick()  * (SystemCoreClock/1000UL)) + systick;
    #else
        return (_tick_count_ms * (SystemCoreClock/1000UL)) + systick;
    #endif
}

// 毫秒计数器: 使用SysTick
uint64_t mcu_clock_ms_now(void) {
    #ifdef USE_HAL_DRIVER
        return HAL_GetTick(); // HAL 的 SysTick 就是 ms 时间戳
    #else
        return _tick_count_ms;
    #endif
}

void mcu_clock_ms_delay(uint32_t num) {
    #ifdef USE_HAL_DRIVER
        HAL_Delay(num);
    #else
        uint32_t curr = _tick_count_ms;
        while(unios_ms_elapsed(curr) < num);
    #endif
}

// 微秒计数器: 使用SysTick
_RAMCODE_ uint64_t mcu_clock_us_now(void) {
    // 获取 SysTick 定时器计数值
    uint32_t us = SysTick->VAL;
    // 把24位向下计数值转换为正向计数值
    us = 0x1000000 - us;
    // 换算成 us
    us /= SystemCoreClock/1000000UL;
    
    // 返回时加上 ms 换算出来的部分
    #ifdef USE_HAL_DRIVER
        // HAL 的 SysTick 就是 ms 时间戳，但本函数要返回的是 SysTick 计数值
        return (HAL_GetTick()  * 1000) + us;
    #else
        return (_tick_count_ms * 1000) + us;
    #endif
}

_RAMCODE_ void mcu_clock_us_delay(uint32_t num) {
    uint32_t curr = mcu_clock_us_now();
    while(unios_us_elapsed(curr) < num);
}

/// 初始化 RTC 时钟, 同时检测时钟是否工作正常
/// @param weekday 0: 星期天, 1~6: 星期一 ~ 星期六
/// @param hour 0~23
static std_err_t _rtc_init() {	
	std_err_t err;
    LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);  // 使能 PWR 时钟
    LL_PWR_EnableBkUpAccess();  						// 使能 RTC 及其备份域寄存器写操作
	
    // 设置 RTC 时钟源
    #if MCU_RTC_CLKSRC_LSE
        // 启用 LSE 时钟（外部 32.768 kHz 晶振）：高精度，需硬件支持
        LL_RCC_LSE_Enable();  								// 启用 LSE 晶振
        while(!LL_RCC_LSE_IsReady());  						// 等待 LSE 稳定
        LL_RCC_SetRTCClockSource(LL_RCC_RTC_CLKSOURCE_LSE); // 选择 LSE 作为 RTC 时钟
    #else
        // 启用 LSI 时钟（内部 RC ≈ 32 kHz）：无需外部元件，但精度较低（±5%）
		LL_RCC_LSI_Enable();								// 启用 LSI 时钟
		while(!LL_RCC_LSI_IsReady());						// 等待 LSI 稳定
		LL_RCC_SetRTCClockSource(LL_RCC_RTC_CLKSOURCE_LSI); // 选择 LSI 作为 RTC 时钟
    #endif
	
    LL_RCC_EnableRTC();  					// 使能 RTC 时钟
	
    LL_RTC_DisableWriteProtection(RTC);  	// 解除写保护(先写0xCA，再写0x53) {{
    LL_RTC_EnterInitMode(RTC);            	// 进入初始化模式 {{
    while(!LL_RTC_IsActiveFlag_INIT(RTC));	// 等待初始化标志

    // 设置分频器(32768/127/255 = 1Hz)
    LL_RTC_SetAsynchPrescaler(RTC, 127);
    LL_RTC_SetSynchPrescaler(RTC, 255);
	
	// 如果已经设置 RTC 时间，就不要再次设置了
	mcu_rtc_bkp_t *bkp = mcu_rtc_bkp_read();
	if(!bkp->rtc_setup) {
		// 设置默认 RTC 时间
		err = mcu_clock_rtc_set(2025, 5, 22, 12, 0, 0); if(err) return err;
		// 写入 RTC 初始化标志，表示已经初始化过
		bkp->rtc_setup = true;
		err = mcu_rtc_bkp_write(bkp); if(err) return err;
	}

    LL_RTC_ExitInitMode(RTC);  				// 退出初始化模式 }}
    LL_RTC_EnableWriteProtection(RTC);  	// 重新启用写保护 }}
    return STD_ERR_OK;
}

uint32_t mcu_clock_get_rtcsec(void) {
    int year, month, day, hour, minute, second;
    mcu_clock_rtc_get(&year, &month, &day, &hour, &minute, &second, NULL);
    unios_datetime_t dt = {{year, month, day}, {hour, minute, second}};
    return dateTime2Seconds(&dt);
}

std_err_t mcu_clock_set_rtcsec(uint32_t sec) {
    unios_datetime_t dt = seconds2DateTime(sec);
    return mcu_clock_rtc_set(dt.date.year, dt.date.month, dt.date.day, dt.time.hour, dt.time.minute, dt.time.second);
}

std_err_t mcu_clock_rtc_set(int year, int month, int day, int hour, int minute, int second) {
    if(year > 9999 || month > 12 || day > 31 || hour > 23 || minute > 60 || second > 60) return STD_ERR_INVALID_ARG;
    unios_date_t dt = {year, month, day};
    uint8_t weekday = date2Weekday(&dt);
    if(weekday == 0) weekday = 7;

    year   = __LL_RTC_CONVERT_BIN2BCD(year   - 2000);
    month  = __LL_RTC_CONVERT_BIN2BCD(month  );
    day    = __LL_RTC_CONVERT_BIN2BCD(day    );
    weekday= __LL_RTC_CONVERT_BIN2BCD(weekday);
    hour   = __LL_RTC_CONVERT_BIN2BCD(hour   );
    minute = __LL_RTC_CONVERT_BIN2BCD(minute );
    second = __LL_RTC_CONVERT_BIN2BCD(second );

    LL_PWR_EnableBkUpAccess();  // 使能备份域写操作 {{
    LL_RTC_DisableWriteProtection(RTC);  // 解除写保护(先写0xCA，再写0x53) {{
	
	LL_RTC_DATE_Config(RTC, weekday, day, month, year);
	LL_RTC_TIME_Config(RTC, LL_RTC_TIME_FORMAT_AM_OR_24, hour, minute, second);

    LL_RTC_EnableWriteProtection(RTC);  // 重新启用写保护 }}
    LL_PWR_DisableBkUpAccess(); // 禁用备份域写操作 }}
    return STD_ERR_OK;
}

std_err_t mcu_clock_rtc_get(int *year, int *month, int *day, int *hour, int *minute, int *second, int *weekday) {
    if(year)    { *year    = LL_RTC_DATE_GetYear(RTC);    *year    = __LL_RTC_CONVERT_BCD2BIN(*year   ) + 2000; }
    if(month)   { *month   = LL_RTC_DATE_GetMonth(RTC);   *month   = __LL_RTC_CONVERT_BCD2BIN(*month  )       ; }
    if(day)     { *day     = LL_RTC_DATE_GetDay(RTC);     *day     = __LL_RTC_CONVERT_BCD2BIN(*day    )       ; }
    if(weekday) { *weekday = LL_RTC_DATE_GetWeekDay(RTC); *weekday = __LL_RTC_CONVERT_BCD2BIN(*weekday) % 7   ; }
    if(hour)    { *hour    = LL_RTC_TIME_GetHour(RTC);    *hour    = __LL_RTC_CONVERT_BCD2BIN(*hour   )       ; }
    if(minute)  { *minute  = LL_RTC_TIME_GetMinute(RTC);  *minute  = __LL_RTC_CONVERT_BCD2BIN(*minute )       ; }
    if(second)  { *second  = LL_RTC_TIME_GetSecond(RTC);  *second  = __LL_RTC_CONVERT_BCD2BIN(*second )       ; }
    return STD_ERR_OK;
}

/// 秒中断回调函数
static mcu_isr_t _rtc_second_handler = NULL;
static void* _rtc_second_handler_cookie = NULL;
/// 闹钟中断回调函数
static mcu_isr_t _rtc_alarm_handler = NULL;
static void* _rtc_alarm_handler_cookie = NULL;

void mcu_clock_set_rtcsec_handler(mcu_isr_t handler, void* cookie) {
    _rtc_second_handler = handler;
    _rtc_second_handler_cookie = cookie;
}

_RAMCODE_ void RTC_Alarm_IRQHandler(void) {
    if(_rtc_alarm_handler) _rtc_alarm_handler(_rtc_alarm_handler_cookie);
}

_RAMCODE_ void RTC_WKUP_IRQHandler(void) {
    if(_rtc_second_handler) _rtc_second_handler(_rtc_second_handler_cookie);
}

std_err_t mcu_clock_set_rtc_alarm(uint32_t timestamp, mcu_isr_t handler, void* cookie) {
    if(timestamp <= mcu_clock_get_rtcsec() || !handler || !cookie) { return STD_ERR_INVALID_ARG; }
	
	// RTC 时钟中断(每秒触发一次)
	
    // 保存回调函数
    _rtc_alarm_handler = handler;
    _rtc_alarm_handler_cookie = cookie;
    return STD_ERR_OK;
}

// RTC 后备区寄存器值缓冲区
static mcu_rtc_bkp_t _bkp;
static uint32_t* const _bkpregs = (uint32_t*)&_bkp;

mcu_rtc_bkp_t* mcu_rtc_bkp_read() {
    for(int i=0; i < sizeof(_bkp) / 4; i++) {
        _bkpregs[i] = LL_RTC_BAK_GetRegister(RTC, i);
    }
    return &_bkp;
}

std_err_t mcu_rtc_bkp_write(mcu_rtc_bkp_t *bkp) {
    if(!bkp) return STD_ERR_INVALID_ARG;
    for(int i=0; i < sizeof(_bkp) / 4; i++) {
        LL_RTC_BAK_SetRegister(RTC, i, _bkpregs[i]);
    }
    return STD_ERR_OK;
}

std_err_t mcu_clock_init() {
    static std_err_t err;
    
    // 配置 HSE + PLL 分频作为系统内核时钟
    err = _mcu_system_core_clock_set_hse_pll(HSE_VALUE, MCU_CLOCK_FREQ);
    retonerr(err, "_mcu_system_core_clock_set_hse_pll(): %s\r\n", std_err_comment(err));

    // 初始化系统滴答时钟: 成功之后 mcu_ms_xxx(), mcu_us_xxx() 相关函数才能正常工作 !!!
    err = _systick_timer_init();
    retonerr(err, "_systick_timer_init(): %s\r\n", std_err_comment(err));

    // 初始化 RTC
    err = _rtc_init();
    retonerr(err, "_rtc_init(): %s\r\n", std_err_comment(err));

    return STD_ERR_OK;
}
